package com.york.tinyrpc.protoss.fault.retry;

import com.github.rholder.retry.*;
import com.york.tinyrpc.protoss.model.TinyRpcResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

/**
 * 指数退避重试
 * 每次重试时间间隔以指数级增加
 */
public class ExponentialBackoffRetryStrategy implements RetryStrategy {

    private static final Logger log = LoggerFactory.getLogger(ExponentialBackoffRetryStrategy.class);

    // 初始延迟时间（单位：秒）
    private static final long INITIAL_DELAY = 3L;
    // 最大重试次数
    private static final int MAX_ATTEMPTS = 3;

    /**
     * 指数退避重试
     *
     * @param target 目标任务
     * @return TinyRpcResponse
     * @throws ExecutionException
     * @throws RetryException
     */
    @Override
    public TinyRpcResponse doRetry(Callable<TinyRpcResponse> target) throws ExecutionException, RetryException {
        Retryer<TinyRpcResponse> retryer = RetryerBuilder.<TinyRpcResponse>newBuilder()
                // 触发条件：遇到任何异常时重试
                .retryIfExceptionOfType(Exception.class)
                // 指数退避策略：初始延迟3秒，后续每次间隔加倍（3s, 6s, 12s...）
                .withWaitStrategy(WaitStrategies.exponentialWait(INITIAL_DELAY, TimeUnit.SECONDS))
                // 停止策略：最多重试3次（含首次调用）
                .withStopStrategy(StopStrategies.stopAfterAttempt(MAX_ATTEMPTS))
                // 重试监听器（记录日志）
                .withRetryListener(new RetryListener() {
                    @Override
                    public <V> void onRetry(Attempt<V> attempt) {
                        long currentTime = attempt.getAttemptNumber();
                        if (currentTime > 1L) {
                            log.info("第 {} 次重试，等待时间：{}秒",
                                    attempt.getAttemptNumber() - 1,
                                    INITIAL_DELAY * Math.pow(2, attempt.getAttemptNumber() - 1));
                        }
                    }
                })
                .build();
        return retryer.call(target);
    }
}
